#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>// malloc，char *str = (char*)malloc(total_size);
#include <cjson/cJSON.h>
#include "file_util.h"
#include "utils/str_util.h"
#include "lv_conf.h"

// 写法	                        实际路径	                 是否推荐
// "config.json"	            当前工作目录（可能是根目录的bin/）	   ⚠️ 不推荐
// "./data/config.json"	        明确、可控、推荐	          ✅ 推荐
// "/Users/.../config.json"	    绝对路径	                 ✅ 可行但不灵活
// "$HOME/.myapp/config.json"	用户配置路径	              ✅ 推荐（桌面程序）

//Q: 数据存储：
// 1️⃣本地存储到文件；
// 2️⃣通过全局变量对数据进行内存临时存储，程序运行期间使用，但是重启程序后会丢失，因此如果要持久化数据则需要采用本地存储到文件；

/**
 * 存储字符串到文件中
 * @param filename 文件路径
 * @param value 字符数组
 * @return 专辑分类标签
 */
_Bool save_value_to_file(const char *filename, const char *value) {
    if (!value) return false;

    FILE *fp = fopen(filename, "w");
    if (!fp) return false;

    // fprintf(fp, value);
    // 编译报错❌：  warning: format string is not a string literal (potentially insecure) [-Wformat-security]
    // 11 |     fprintf(fp, value);
    // 原因分析✅：value是一个变量，而不是固定的字符串字面量。fprintf的第一个参数是文件指针，第二个参数是格式字符串，
    // 如果直接把变量传进去，编译器会报警告，原因是存在格式化字符串漏洞（比如value中包含%s、%d等，会被解析成参数，
    // 从而可能导致崩溃或安全问题）。正确写法应该总是提供格式字符串，然后把变量当作参数传入，如下：
    fprintf(fp, "%s", value);

    fclose(fp);
    return true;
}

/**
 * 判断文件是否存在
 * @param file_path 文件路径，例如："./assets/images/cymbal/cymbal_00.jpg";
 * @return bool 文件是否存在
 */
_Bool is_file_exist(const char *file_path) {
    if (!file_path) return false;
    // FILE *fp = fopen(file_path, "r");//✅以文本模式读取，系统会对换行符进行转换，适合人类可读文本：Windows 下自动把文件中的 \r\n 读成 \n；写入时也反之。
    FILE *fp = fopen(file_path, "rb");//✅以二进制模式读取，不做任何字符转换，原样读取字节，适合机器处理的文件，适合读取图片、视频、JSON、加密文件等。
    //只判断文件是否存在、能否被打开时，不需要读取内容，"rb"相对"r"来说，跨平台更安全、更可移植、统一
    if (fp) {
        fclose(fp);
        return true;
    }
    return false;
}

/**
 * 从文件读取存储的字符串到
 * @param file_path 文件路径
 * @return char * 存储字符串
 */
char *read_from_file(const char *file_path) {
    if (!file_path) return NULL;// ❌文件路径文件名为NULL
    if (is_file_exist(file_path))
    {
        // ✅fopen(filename, "rb") 是 C 标准库函数，用于打开文件。
        // filename：要打开的文件路径（例如 "./data/config.json"）
        // "rb"：打开模式字符串，以二进制方式只读打开一个文件
        // "r" 表示以只读模式打开；
        // "b" 表示二进制模式（binary mode），防止某些系统自动处理换行符。
        // 返回“文件对象指针”，一种结构体指针，用来操作打开的文件：fread(fp, ...)读取文件；fwrite(fp, ...)写文件；fclose(fp)关闭文件。
        // fopen()如果在打开文件失败时会返回 NULL。
        FILE *fp = fopen(file_path, "rb");
        if (!fp) return NULL;// ❌打开文件失败，文件对象指针为NULL

        // 获取文件大小
        fseek(fp, 0, SEEK_END);
        long file_size = ftell(fp);
        fseek(fp, 0, SEEK_SET);

        if (file_size <= 0) {
            fclose(fp);
            // ❌文件为空，返回空字符数组，不是 malloc 的堆内存，不能调用free对该返回值释放内存
            return "";
        }

        // 分配缓冲区并读取整个文件
        char *buffer = (char*)malloc(file_size + 1);
        if (!buffer) {
            fclose(fp);
            // ❌分配缓冲区失败
            return NULL;
        }

        size_t read_size = fread(buffer, 1, file_size, fp);
        buffer[read_size] = '\0'; // 末尾加\0
        fclose(fp);
        // ✅文件存在并被成功读取
        return buffer;
    }
    // ❌文件不存在
    return NULL;
}

/**
 * cjson对象序列化成字符串存储到文件内
 * @param filename 文件路径
 * @param json cJSON对象
 * @return 是否存储成功
 */
_Bool save_json_to_file(const char *filename, cJSON *json) {
    char *json_str = json_to_string(json);
    // 1️⃣ 序列化为字符串
    if (is_empty_str(json_str)) return false;

    // 2️⃣ 写入文件
    bool result = save_value_to_file(filename, json_str);

    // 3️⃣ 释放 cJSON 生成的字符串
    free(json_str);
    return result;
}

/**
 * ✅ 从文件中读取字符串反序列化回 cJSON 对象
 * @param filename 文件路径
 * @return cJSON对象
 */
cJSON *read_json_from_file(const char *file_path) {
    char *json_str = read_from_file(file_path);
    if (is_empty_str(json_str)) return NULL;

    cJSON *json = cJSON_Parse(json_str);
    // 通过is_empty_str确保read_from_file函数方法返回的json_str为非空字符串，如果为字面量空字符串常量，会导致编译报错❌：
    // main(48186,0x20055c200) malloc: *** error for object 0x1002f6839: pointer being freed was not allocated
    // main(48186,0x20055c200) malloc: *** set a breakpoint in malloc_error_break to debug
    free(json_str);  // 释放 read_json_from_file 分配的内存
    return json;
}

/**
 * 确保 DATA_DIR 存在，如果不存在则创建
 * 所有存储文件的文件夹都通过该方法确保创建存在，在main.c主函数执行该方法
 */
void ensure_data_dir_exists(void) {
    // 创建目录
    #ifdef _WIN32
        char cmd[256];
        snprintf(cmd, sizeof(cmd), "mkdir \"%s\"", DATA_DIR);
        system(cmd);
    #else
        // 使用前自动确保目录存在，在项目根目录所在路径下创建data文件夹，跟代码所在.c文件的路径无关
        // 假设项目根目录在：/Users/you/project/，执行后会在：/Users/you/project/data/创建文件夹。
        //-p参数：创建多级目录（如果父目录不存在也会创建），如果目录已经存在，不报错
        char cmd[256];
        snprintf(cmd, sizeof(cmd), "mkdir -p \"%s\"", DATA_DIR);
        system(cmd);
    #endif
}

/**
 * 拼接项目根目录和 assets 内相对路径
 * @param relative_path 例如 "images/cymbal/cymbal_00.jpg"
 * @return 静态字符串指针，可直接用于 lv_image_set_src()
 */
char *get_asset_path(char *relative_path) {
    char cwd[MAX_PATH_LEN];

    if (getcwd(cwd, sizeof(cwd)) == NULL) {
        perror("getcwd failed");
        return relative_path; // fallback
    }

    char *full_path = malloc(MAX_PATH_LEN);
    if (!full_path) return relative_path;

    // 确保assets文件夹在项目根目录
    // ✅此处驱动号为A，驱动号设置方式：lv_conf.h的LV_FS_STDIO_LETTER宏定义设置驱动号：#define LV_FS_STDIO_LETTER 'A'
    snprintf(full_path, MAX_PATH_LEN,
             "%c:%s%sassets%s%s",
             LV_FS_STDIO_LETTER, cwd, PATH_SEP, PATH_SEP, relative_path);

    return full_path;
}
